package com.ssm.security;

import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.compression.CompressionCodecs;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.io.IOException;
import java.util.*;

/**
 * Created by asd73 on 2017/7/22.
 */
public class JWTUtils {
    public static final String ROLE_REFRESH_TOKEN = "ROLE_REFRESH_TOKEN";

    private static final String CLAIM_KEY_USER_ID = "user_id";
    private static final String CLAIM_KEY_AUTHORITIES = "scope";
    private static final String CLAIM_KEY_USER_IPADDRESS = "user_IPAddress";
    private static final String CLAIM_KEY_ACCOUNT_ENABLED = "enabled";
    private static final String CLAIM_KEY_ACCOUNT_NON_LOCKED = "non_locked";
    private static final String CLAIM_KEY_ACCOUNT_NON_EXPIRED = "non_expired";

    private final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
    private final String SECRET = "P@ssw02d";

    @Value("${ACCESS_TOKEN_EXPIRATION}")
    private long ACCESS_TOKEN_EXPIRATION;

    private final long REFRESH_TOKEN_EXPIRATION = 604800;//单位秒。程序中会*1000

    public JWTUserDetails getUserFromToken(String token) throws IOException {
        JWTUserDetails user;
        try {
            final Claims claims = getClaimsFromToken(token);
            long userId = getUserIdFromToken(token);
            String username = claims.getSubject();
            List roles = (List) claims.get(CLAIM_KEY_AUTHORITIES);
            Collection<? extends GrantedAuthority> authorities = parseArrayToAuthorities(roles);
            boolean account_enabled = (Boolean) claims.get(CLAIM_KEY_ACCOUNT_ENABLED);
            boolean account_non_locked = (Boolean) claims.get(CLAIM_KEY_ACCOUNT_NON_LOCKED);
            boolean account_non_expired = (Boolean) claims.get(CLAIM_KEY_ACCOUNT_NON_EXPIRED);

            user = new JWTUserDetails(userId, username, "password", account_enabled, account_non_expired, true, account_non_locked, authorities);
        } catch (Exception e) {
            user = null;
            throw new IOException("Illegal token data");
        }
        return user;
    }

    public long getUserIdFromToken(String token) throws IOException {
        long userId;
        try {
            final Claims claims = getClaimsFromToken(token);
            //userId = (Long) claims.get(CLAIM_KEY_USER_ID);
            userId = Long.parseLong(claims.get(CLAIM_KEY_USER_ID).toString());
        } catch (Exception e) {
            userId = 0;
            throw new IOException("Illegal token data");
        }
        return userId;
    }

    public String getUsernameFromToken(String token) throws IOException {
        String username;
        try {
            final Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
            throw new IOException(e.getMessage());
        }
        return username;
    }

    public Date getCreatedDateFromToken(String token) {
        Date created;
        try {
            final Claims claims = getClaimsFromToken(token);
            created = claims.getIssuedAt();
        } catch (Exception e) {
            created = null;
        }
        return created;
    }

    public Date getExpirationDateFromToken(String token) throws IOException {
        Date expiration;
        try {
            final Claims claims = getClaimsFromToken(token);
            expiration = claims.getExpiration();
        } catch (Exception e) {
            expiration = null;
            throw new IOException("Illegal token data");
        }
        return expiration;
    }

    public String getUserIPAddress(String token) throws IOException {
        String userIPAddress;
        try {
            final Claims claims = getClaimsFromToken(token);
            userIPAddress =claims.get(CLAIM_KEY_USER_IPADDRESS).toString();
        } catch (IOException e) {
            userIPAddress=null;
            throw new IOException("Illegal token data");

        }
        return userIPAddress;
    }

    private Claims getClaimsFromToken(String token) throws IOException {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(SECRET)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            claims = null;
            throw new IOException(e.getMessage());
        }
        return claims;
    }

    private Date generateExpirationDate(Long expiration) {
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

    private Boolean isTokenExpired(String token) throws IOException {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
        return (lastPasswordReset != null && created.before(lastPasswordReset));
    }

    public String generateAccessToken(JWTUserDetails userDetails,String userIPAddress) throws IOException {
        Map<String, Object> claims = generateClaims(userDetails,userIPAddress);
        claims.put(CLAIM_KEY_AUTHORITIES, JSON.toJSON(authoritiesToArray(userDetails.getAuthorities())));
        return generateAccessToken(userDetails.getUsername(), claims);
    }

    private Map<String, Object> generateClaims(JWTUserDetails user,String userIPAddress) {
        Map<String, Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USER_ID, user.getUserId());
        claims.put(CLAIM_KEY_USER_IPADDRESS, userIPAddress) ;
        claims.put(CLAIM_KEY_ACCOUNT_ENABLED, user.isEnabled());
        claims.put(CLAIM_KEY_ACCOUNT_NON_LOCKED, user.isAccountNonLocked());
        claims.put(CLAIM_KEY_ACCOUNT_NON_EXPIRED, user.isAccountNonExpired()) ;
        return claims;
    }

    private String generateAccessToken(String subject, Map<String, Object> claims) throws IOException {
        return generateToken(subject, claims, ACCESS_TOKEN_EXPIRATION);
    }

    private List authoritiesToArray(Collection<? extends GrantedAuthority> authorities) {
        List<String> list = new ArrayList<>();
        for (GrantedAuthority ga : authorities) {
            list.add(ga.getAuthority());
        }
        return list;
    }

    private Collection<? extends GrantedAuthority> parseArrayToAuthorities(List roles) {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        SimpleGrantedAuthority authority;
        for (Object role : roles) {
            authority = new SimpleGrantedAuthority(role.toString());
            authorities.add(authority);
        }
        return authorities;
    }

    public String generateRefreshToken(UserDetails userDetails,String userIPAddress) throws IOException {
        JWTUserDetails user = (JWTUserDetails) userDetails;
        Map<String, Object> claims = generateClaims(user,userIPAddress);
        // 只授于更新 token 的权限
        String roles[] = new String[]{JWTUtils.ROLE_REFRESH_TOKEN};
        claims.put(CLAIM_KEY_AUTHORITIES, JSON.toJSON(roles));
        return generateRefreshToken(user.getUsername(), claims);
    }

    private String generateRefreshToken(String subject, Map<String, Object> claims) throws IOException {
        return generateToken(subject, claims, REFRESH_TOKEN_EXPIRATION);
    }

    public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) throws IOException {
        final Date created = getCreatedDateFromToken(token);
        return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
                && (!isTokenExpired(token));
    }

    public String refreshToken(String token) {
        String refreshedToken;
        try {
            final Claims claims = getClaimsFromToken(token);
            refreshedToken = generateAccessToken(claims.getSubject(), claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    private String generateToken(String subject, Map<String, Object> claims, Long expiration) throws IOException {
        String jwt = null;
        try {
            jwt = Jwts.builder()
                    .setClaims(claims)
                    .setSubject(subject)
                    .setId(UUID.randomUUID().toString())
                    .setIssuedAt(new Date())
                    .setExpiration(generateExpirationDate(expiration))
                    .compressWith(CompressionCodecs.DEFLATE)
                    .signWith(SIGNATURE_ALGORITHM, SECRET)
                    .compact();
        } catch (Exception e) {
            throw new IOException("generateToken failed");
        }
        return jwt;
    }

    public Boolean validateToken(String token, JWTUserDetails jwtUserDetails, String username) throws IOException {//username不能为空！！！
        final long userId = getUserIdFromToken(token);
        if (username != null && userId != 0){
            return (userId == jwtUserDetails.getUserId()
                    && username.equals(jwtUserDetails.getUsername())
                    && !isTokenExpired(token)
                /* && !isCreatedBeforeLastPasswordReset(created, userDetails.getLastPasswordResetDate()) */
            );
        }
        return false;
        // final Date created = getCreatedDateFromToken(token);
        // final Date expiration = getExpirationDateFromToken(token);
    }

}

